<?php
namespace App\Http\Controllers\Pay;

use App\Http\Controllers\Controller;
use DB;
use Redirect;

class WxPayNotifyController extends Controller {
	
	protected $values = array();
	
	public function __construct(){
		header("Content-type:text/html;charset=utf-8");
		//微信支付API
		require_once(app_path().'/Library/Wxpay/lib/WxPayConfig.php');
		require_once(app_path().'/Library/Wxpay/lib/WxPayData.php');
		require_once(app_path().'/Library/Wxpay/lib/WxPayException.php');
		require_once(app_path().'/Library/Wxpay/lib/WxPayApi.php');
		require_once(app_path().'/Library/Wxpay/log.php');
		
		date_default_timezone_set('Asia/Shanghai');	
    }
	
	/**
	 * 订单号 更新订单状态的函数
	 * 结果一定要返回 true / false
	 */
	function wxpayend($out_trade_no, $transaction_id){
		
		//此处为变更订单状态业务逻辑
		$data = [
			'transaction_id'=>$transaction_id,
			'record_status' => 1,
			'record_pay_time'=>time(),
		];
		$result = DB::table('record')->where('out_trade_no', $out_trade_no)->update($data);

		if($result){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	 * 
	 * 回调入口
	 * @param bool $needSign  是否需要签名输出
	 */
	final public function Handle($needSign = true)
	{
		$msg = "OK";
		
		//当返回false的时候，表示notify中调用NotifyCallBack回调失败获取签名校验失败，此时直接回复失败
		$result = \WxpayApi::notify(array($this, 'NotifyCallBack'), $msg);
		
		if($result == false){
			$this->values['return_code'] = "FAIL";
			$this->values['return_msg'] = $msg;
			$this->ReplyNotify(false);
			return;
		} else {
			//该分支在成功回调到NotifyCallBack方法，处理完成之后流程
			$this->values['return_code'] = "SUCCESS";
			$this->values['return_msg'] = "OK";
		}
		$this->ReplyNotify($needSign);
	}
	
	/**
	 * 
	 * 回调方法入口，子类可重写该方法
	 * 注意：
	 * 1、微信回调超时时间为2s，建议用户使用异步处理流程，确认成功之后立刻回复微信服务器
	 * 2、微信服务器在调用失败或者接到回包为非确认包的时候，会发起重试，需确保你的回调是可以重入
	 * @param array $data 回调解释出的参数
	 * @param string $msg 如果回调处理失败，可以将错误信息输出到该方法
	 * @return true回调出来完成不需要继续回调，false回调处理未完成需要继续回调
	 */
	public function NotifyProcess($data,&$msg){
		
		if(!array_key_exists("transaction_id", $data)){
			$msg = "输入参数不正确";
			return false;
		}
		//根据 $data["out_trade_no"] 订单号 更新订单状态
		$re = $this->wxpayend($data["out_trade_no"],$data["transaction_id"]);
		if($re){
			return true;
		}else{
			$msg = "订单状态异常！";
			return false;
		}
	}
	
	/**
	 * 
	 * notify回调方法，该方法中需要赋值需要输出的参数,不可重写
	 * @param array $data
	 * @return true回调出来完成不需要继续回调，false回调处理未完成需要继续回调
	 */
	final public function NotifyCallBack($data)
	{
		$msg = "OK";
		$result = $this->NotifyProcess($data, $msg);
		if($result == true){
			$this->values['return_code'] = "SUCCESS";
			$this->values['return_msg'] = "OK";
		} else {
			$this->values['return_code'] = "FAIL";
			$this->values['return_msg'] = $msg;
		}
		return $result;
	}
	
	/**
	 * 
	 * 回复通知
	 * @param bool $needSign 是否需要签名输出
	 */
	final private function ReplyNotify($needSign = true)
	{
		//如果需要签名
		if($needSign == true &&	$this->values['return_code'] == "SUCCESS")
		{
			$this->SetSign();
		}
		\WxpayApi::replyNotify($this->ToXml());
	}
	
	function wxnotify(){
		$this->Handle(false);
	}
	
	/**
	* 设置签名，详见签名生成算法
	* @param string $value 
	**/
	public function SetSign()
	{
		$sign = $this->MakeSign();
		$this->values['sign'] = $sign;
		return $sign;
	}
	
	/**
	 * 生成签名
	 * @return 签名，本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
	 */
	public function MakeSign()
	{
		//签名步骤一：按字典序排序参数
		ksort($this->values);
		$string = $this->ToUrlParams();
		//签名步骤二：在string后加入KEY
		$string = $string . "&key=".WxPayConfig::KEY;
		//签名步骤三：MD5加密
		$string = md5($string);
		//签名步骤四：所有字符转为大写
		$result = strtoupper($string);
		return $result;
	}
	
	/**
	 * 格式化参数格式化成url参数
	 */
	public function ToUrlParams()
	{
		$buff = "";
		foreach ($this->values as $k => $v)
		{
			if($k != "sign" && $v != "" && !is_array($v)){
				$buff .= $k . "=" . $v . "&";
			}
		}
		
		$buff = trim($buff, "&");
		return $buff;
	}
	
	/**
	 * 输出xml字符
	 * @throws WxPayException
	**/
	public function ToXml()
	{
		if(!is_array($this->values) 
			|| count($this->values) <= 0)
		{
    		throw new \WxPayException("数组数据异常！");
    	}
    	
    	$xml = "<xml>";
    	foreach ($this->values as $key=>$val)
    	{
    		if (is_numeric($val)){
    			$xml.="<".$key.">".$val."</".$key.">";
    		}else{
    			$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
    		}
        }
        $xml.="</xml>";
        return $xml; 
	}
}